<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>HttpClient - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/httpclient.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/httpclient.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>HttpClient</h1>
    <p>互联网时代，无数服务是基于 HTTP 协议进行通信的，Web 应用调用后端 HTTP 服务是一种非常常见的应用场景。</p>
<p>为此框架基于 <a href="https://github.com/node-modules/urllib" target="_blank" rel="noopener">urllib</a> 内置实现了一个 <a href="https://github.com/eggjs/egg/blob/master/lib/core/httpclient.js" target="_blank" rel="noopener">HttpClient</a>，应用可以非常便捷地完成任何 HTTP 请求。</p>
<h2 id="通过-app-使用-httpclient"><a class="markdown-anchor" href="#通过-app-使用-httpclient">#</a> 通过 app 使用 HttpClient</h2>
<p>框架在应用初始化的时候，会自动将 <a href="https://github.com/eggjs/egg/blob/master/lib/core/httpclient.js" target="_blank" rel="noopener">HttpClient</a> 初始化到 <code>app.httpclient</code>。
同时增加了一个 <code>app.curl(url, options)</code> 方法，它等价于 <code>app.httpclient.request(url, options)</code>。</p>
<p>这样就可以非常方便地使用 <code>app.curl</code> 方法完成一次 HTTP 请求。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="comment">// 示例：启动的时候去读取 https://registry.npm.taobao.org/egg/latest 的版本信息</span></span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> app.curl(<span class="string">'https://registry.npm.taobao.org/egg/latest'</span>, &#123;</span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    app.logger.info(<span class="string">'Egg latest version: %s'</span>, result.data.version);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="通过-context-使用-httpclient"><a class="markdown-anchor" href="#通过-context-使用-httpclient">#</a> 通过 Context 使用 HttpClient</h2>
<p>框架在 Context 中同样提供了 <code>ctx.curl(url, options)</code> 和 <code>ctx.httpclient</code>，保持跟 app 下的使用体验一致。
这样就可以在有 Context 的地方（如在 controller 中）非常方便地使用 <code>ctx.curl()</code> 方法完成一次 HTTP 请求。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> index() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 示例：请求一个 npm 模块信息</span></span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://registry.npm.taobao.org/egg/latest'</span>, &#123;</span><br><span class="line">      <span class="comment">// 自动解析 JSON response</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">      <span class="comment">// 3 秒超时</span></span><br><span class="line">      timeout: <span class="number">3000</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    ctx.body = &#123;</span><br><span class="line">      status: result.status,</span><br><span class="line">      headers: result.headers,</span><br><span class="line">      package: result.data,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="基本-http-请求"><a class="markdown-anchor" href="#基本-http-请求">#</a> 基本 HTTP 请求</h2>
<p>HTTP 已经被广泛大量使用，尽管 HTTP 有多种请求方式，但是万变不离其宗，我们先以基本的4个请求方法为例子，
逐步讲解一下更多的复杂应用场景。</p>
<p>以下例子都会在 controller 代码中对 https://httpbin.org 发起请求来完成。</p>
<h3 id="get"><a class="markdown-anchor" href="#get">#</a> GET</h3>
<p>读取数据几乎都是使用 GET 请求，它是 HTTP 世界最常见的一种，也是最广泛的一种，它的请求参数也是最容易构造的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> get() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/get?foo=bar'</span>);</span><br><span class="line">    ctx.status = result.status;</span><br><span class="line">    ctx.set(result.headers);</span><br><span class="line">    ctx.body = result.data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>GET 请求可以不用设置 <code>options.method</code> 参数，HttpClient 的默认 method 会设置为 <code>GET</code>。</li>
<li>返回值 <code>result</code> 会包含 3 个属性：<code>status</code>, <code>headers</code> 和 <code>data</code>
<ul>
<li><code>status</code>: 响应状态码，如 <code>200</code>, <code>302</code>, <code>404</code>, <code>500</code> 等等</li>
<li><code>headers</code>: 响应头，类似 <code>{ 'content-type': 'text/html', ... }</code></li>
<li><code>data</code>: 响应 body，默认 HttpClient 不会做任何处理，会直接返回 Buffer 类型数据。
一旦设置了 <code>options.dataType</code>，HttpClient 将会根据此参数对 <code>data</code> 进行相应的处理。</li>
</ul>
</li>
</ul>
<p>完整的请求参数 <code>options</code> 和返回值 <code>result</code> 的说明请看下文的 <a href="#options-%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3">options 参数详解</a> 章节。</p>
<h3 id="post"><a class="markdown-anchor" href="#post">#</a> POST</h3>
<p>创建数据的场景一般来说都会使用 POST 请求，它相对于 GET 来说多了请求 body 这个参数。</p>
<p>以发送 JSON body 的场景举例：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> post() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/post'</span>, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method</span></span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">      <span class="comment">// 通过 contentType 告诉 HttpClient 以 JSON 格式发送</span></span><br><span class="line">      contentType: <span class="string">'json'</span>,</span><br><span class="line">      data: &#123;</span><br><span class="line">        hello: <span class="string">'world'</span>,</span><br><span class="line">        now: <span class="built_in">Date</span>.now(),</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.body = result.data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下文还会详细讲解以 POST 实现 Form 表单提交和文件上传的功能。</p>
<h3 id="put"><a class="markdown-anchor" href="#put">#</a> PUT</h3>
<p>PUT 与 POST 类似，它更加适合更新数据和替换数据的语义。
除了 method 参数需要设置为 <code>PUT</code>，其他参数几乎跟 POST 一模一样。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> put() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/put'</span>, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method</span></span><br><span class="line">      method: <span class="string">'PUT'</span>,</span><br><span class="line">      <span class="comment">// 通过 contentType 告诉 HttpClient 以 JSON 格式发送</span></span><br><span class="line">      contentType: <span class="string">'json'</span>,</span><br><span class="line">      data: &#123;</span><br><span class="line">        update: <span class="string">'foo bar'</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 明确告诉 HttpClient 以 JSON 格式处理响应 body</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.body = result.data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="delete"><a class="markdown-anchor" href="#delete">#</a> DELETE</h3>
<p>删除数据会选择 DELETE 请求，它通常可以不需要加请求 body，但是 HttpClient 不会限制。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> del() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/delete'</span>, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method</span></span><br><span class="line">      method: <span class="string">'DELETE'</span>,</span><br><span class="line">      <span class="comment">// 明确告诉 HttpClient 以 JSON 格式处理响应 body</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.body = result.data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="高级-http-请求"><a class="markdown-anchor" href="#高级-http-请求">#</a> 高级 HTTP 请求</h2>
<p>在真实的应用场景下，还是会包含一些较为复杂的 HTTP 请求。</p>
<h3 id="form-表单提交"><a class="markdown-anchor" href="#form-表单提交">#</a> Form 表单提交</h3>
<p>面向浏览器设计的 Form 表单（不包含文件）提交接口，通常都要求以 <code>content-type: application/x-www-form-urlencoded</code>
的格式提交请求数据。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> submit() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/post'</span>, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method，支持 POST，PUT 和 DELETE</span></span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">      <span class="comment">// 不需要设置 contentType，HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求</span></span><br><span class="line">      data: &#123;</span><br><span class="line">        now: <span class="built_in">Date</span>.now(),</span><br><span class="line">        foo: <span class="string">'bar'</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 明确告诉 HttpClient 以 JSON 格式处理响应 body</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.body = result.data.form;</span><br><span class="line">    <span class="comment">// 响应最终会是类似以下的结果：</span></span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//   "foo": "bar",</span></span><br><span class="line">    <span class="comment">//   "now": "1483864184348"</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="以-multipart-方式上传文件"><a class="markdown-anchor" href="#以-multipart-方式上传文件">#</a> 以 Multipart 方式上传文件</h3>
<p>当一个 Form 表单提交包含文件的时候，请求数据格式就必须以 <a href="http://tools.ietf.org/html/rfc2388" target="_blank" rel="noopener">multipart/form-data</a>
进行提交了。
这个时候需要引入 <a href="https://github.com/node-modules/formstream" target="_blank" rel="noopener">formstream</a> 这个第三方模块来帮助我们生成可以被 HttpClient 消费的 <code>form</code> 对象。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="keyword">const</span> FormStream = <span class="built_in">require</span>(<span class="string">'formstream'</span>);</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> upload() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> form = <span class="keyword">new</span> FormStream();</span><br><span class="line">    <span class="comment">// 设置普通的 key value</span></span><br><span class="line">    form.field(<span class="string">'foo'</span>, <span class="string">'bar'</span>);</span><br><span class="line">    <span class="comment">// 上传当前文件本身用于测试</span></span><br><span class="line">    form.file(<span class="string">'file'</span>, __filename);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(<span class="string">'https://httpbin.org/post'</span>, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method，支持 POST，PUT</span></span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">      <span class="comment">// 生成符合 multipart/form-data 要求的请求 headers</span></span><br><span class="line">      headers: form.headers(),</span><br><span class="line">      <span class="comment">// 以 stream 模式提交</span></span><br><span class="line">      stream: form,</span><br><span class="line">      <span class="comment">// 明确告诉 HttpClient 以 JSON 格式处理响应 body</span></span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.body = result.data.files;</span><br><span class="line">    <span class="comment">// 响应最终会是类似以下的结果：</span></span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//   "file": "'use strict';\n\nconst For...."</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当然，你还可以继续通过 <code>form.file()</code> 添加更多文件以实现一次性上传多个文件的需求。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">form.file(<span class="string">'file1'</span>, file1);</span><br><span class="line">form.file(<span class="string">'file2'</span>, file2);</span><br></pre></td></tr></table></figure>
<h3 id="以-stream-方式上传文件"><a class="markdown-anchor" href="#以-stream-方式上传文件">#</a> 以 Stream 方式上传文件</h3>
<p>其实，在 Node.js 的世界里面，Stream 才是主流。
如果服务端支持流式上传，最友好的方式还是直接发送 Stream。
Stream 实际会以 <code>Transfer-Encoding: chunked</code> 传输编码格式发送，这个转换是 <a href="https://nodejs.org/api/http.html" target="_blank" rel="noopener">HTTP</a> 模块自动实现的。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/npm.js</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> FormStream = <span class="built_in">require</span>(<span class="string">'formstream'</span>);</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NpmController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> uploadByStream() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="comment">// 上传当前文件本身用于测试</span></span><br><span class="line">    <span class="keyword">const</span> fileStream = fs.createReadStream(__filename);</span><br><span class="line">    <span class="comment">// httpbin.org 不支持 stream 模式，使用本地 stream 接口代替</span></span><br><span class="line">    <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;ctx.protocol&#125;</span>://<span class="subst">$&#123;ctx.host&#125;</span>/stream`</span>;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(url, &#123;</span><br><span class="line">      <span class="comment">// 必须指定 method，支持 POST，PUT</span></span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">      <span class="comment">// 以 stream 模式提交</span></span><br><span class="line">      stream: fileStream,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.status = result.status;</span><br><span class="line">    ctx.set(result.headers);</span><br><span class="line">    ctx.body = result.data;</span><br><span class="line">    <span class="comment">// 响应最终会是类似以下的结果：</span></span><br><span class="line">    <span class="comment">// &#123;"streamSize":574&#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="options-参数详解"><a class="markdown-anchor" href="#options-参数详解">#</a> options 参数详解</h2>
<p>由于 HTTP 请求的复杂性，导致 <code>httpclient.request(url, options)</code> 的 options 参数会非常多。
接下来将会以参数说明和代码配合一起讲解每个可选参数的实际用途。</p>
<h3 id="httpclient-默认全局配置"><a class="markdown-anchor" href="#httpclient-默认全局配置">#</a> HttpClient 默认全局配置</h3>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.httpclient = &#123;</span><br><span class="line">  <span class="comment">// 是否开启本地 DNS 缓存，默认关闭，开启后有两个特性</span></span><br><span class="line">  <span class="comment">// 1. 所有的 DNS 查询都会默认优先使用缓存的，即使 DNS 查询错误也不影响应用</span></span><br><span class="line">  <span class="comment">// 2. 对同一个域名，在 dnsCacheLookupInterval 的间隔内（默认 10s）只会查询一次</span></span><br><span class="line">  enableDNSCache: <span class="literal">false</span>,</span><br><span class="line">  <span class="comment">// 对同一个域名进行 DNS 查询的最小间隔时间</span></span><br><span class="line">  dnsCacheLookupInterval: <span class="number">10000</span>,</span><br><span class="line">  <span class="comment">// DNS 同时缓存的最大域名数量，默认 1000</span></span><br><span class="line">  dnsCacheMaxLength: <span class="number">1000</span>,</span><br><span class="line"></span><br><span class="line">  request: &#123;</span><br><span class="line">    <span class="comment">// 默认 request 超时时间</span></span><br><span class="line">    timeout: <span class="number">3000</span>,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  httpAgent: &#123;</span><br><span class="line">    <span class="comment">// 默认开启 http KeepAlive 功能</span></span><br><span class="line">    keepAlive: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// 空闲的 KeepAlive socket 最长可以存活 4 秒</span></span><br><span class="line">    freeSocketKeepAliveTimeout: <span class="number">4000</span>,</span><br><span class="line">    <span class="comment">// 当 socket 超过 30 秒都没有任何活动，就会被当作超时处理掉</span></span><br><span class="line">    timeout: <span class="number">30000</span>,</span><br><span class="line">    <span class="comment">// 允许创建的最大 socket 数</span></span><br><span class="line">    maxSockets: <span class="built_in">Number</span>.MAX_SAFE_INTEGER,</span><br><span class="line">    <span class="comment">// 最大空闲 socket 数</span></span><br><span class="line">    maxFreeSockets: <span class="number">256</span>,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  httpsAgent: &#123;</span><br><span class="line">    <span class="comment">// 默认开启 https KeepAlive 功能</span></span><br><span class="line">    keepAlive: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// 空闲的 KeepAlive socket 最长可以存活 4 秒</span></span><br><span class="line">    freeSocketKeepAliveTimeout: <span class="number">4000</span>,</span><br><span class="line">    <span class="comment">// 当 socket 超过 30 秒都没有任何活动，就会被当作超时处理掉</span></span><br><span class="line">    timeout: <span class="number">30000</span>,</span><br><span class="line">    <span class="comment">// 允许创建的最大 socket 数</span></span><br><span class="line">    maxSockets: <span class="built_in">Number</span>.MAX_SAFE_INTEGER,</span><br><span class="line">    <span class="comment">// 最大空闲 socket 数</span></span><br><span class="line">    maxFreeSockets: <span class="number">256</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>应用可以通过 <code>config/config.default.js</code> 覆盖此配置。</p>
<h3 id="data-object"><a class="markdown-anchor" href="#data-object">#</a> <code>data: Object</code></h3>
<p>需要发送的请求数据，根据 <code>method</code> 自动选择正确的数据处理方式。</p>
<ul>
<li>GET，HEAD：通过 <code>querystring.stringify(data)</code> 处理后拼接到 url 的 query 参数上。</li>
<li>POST，PUT 和 DELETE 等：需要根据 <code>contentType</code> 做进一步判断处理。
<ul>
<li><code>contentType = json</code>：通过 <code>JSON.stringify(data)</code> 处理，并设置为 body 发送。</li>
<li>其他：通过 <code>querystring.stringify(data)</code> 处理，并设置为 body 发送。</li>
</ul>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// GET + data</span></span><br><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  data: &#123; <span class="attr">foo</span>: <span class="string">'bar'</span> &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// POST + data</span></span><br><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  data: &#123; <span class="attr">foo</span>: <span class="string">'bar'</span> &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// POST + JSON + data</span></span><br><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  contentType: <span class="string">'json'</span>,</span><br><span class="line">  data: &#123; <span class="attr">foo</span>: <span class="string">'bar'</span> &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="dataasquerystring-boolean"><a class="markdown-anchor" href="#dataasquerystring-boolean">#</a> <code>dataAsQueryString: Boolean</code></h3>
<p>如果设置了 <code>dataAsQueryString=true</code>，那么即使在 POST 情况下，
也会强制将 <code>options.data</code> 以 <code>querystring.stringify</code> 处理之后拼接到 <code>url</code> 的 query 参数上。</p>
<p>可以很好地解决以 <code>stream</code> 发送数据，且额外的请求参数以 <code>url</code> query 形式传递的应用场景：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  dataAsQueryString: <span class="literal">true</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    <span class="comment">// 一般来说都是 access token 之类的权限验证参数</span></span><br><span class="line">    accessToken: <span class="string">'some access token value'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  stream: myFileStream,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="content-stringbuffer"><a class="markdown-anchor" href="#content-stringbuffer">#</a> <code>content: String|Buffer</code></h3>
<p>发送请求正文，如果设置了此参数，那么会直接忽略 <code>data</code> 参数。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  <span class="comment">// 直接发送原始 xml 数据，不需要 HttpClient 做特殊处理</span></span><br><span class="line">  content: <span class="string">'&lt;xml&gt;&lt;hello&gt;world&lt;/hello&gt;&lt;/xml&gt;'</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">'content-type'</span>: <span class="string">'text/html'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="stream-readstream"><a class="markdown-anchor" href="#stream-readstream">#</a> <code>stream: ReadStream</code></h3>
<p>设置发送请求正文的可读数据流，默认是 <code>null</code>。
一旦设置了此参数，HttpClient 将会忽略 <code>data</code> 和 <code>content</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  stream: fs.createReadStream(<span class="string">'/path/to/read'</span>),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="writestream-writestream"><a class="markdown-anchor" href="#writestream-writestream">#</a> <code>writeStream: WriteStream</code></h3>
<p>设置接受响应数据的可写数据流，默认是 <code>null</code>。
一旦设置此参数，那么返回值 <code>result.data</code> 将会被设置为 <code>null</code>，
因为数据已经全部写入到 <code>writeStream</code> 中了。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  writeStream: fs.createWriteStream(<span class="string">'/path/to/store'</span>),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="consumewritestream-boolean"><a class="markdown-anchor" href="#consumewritestream-boolean">#</a> <code>consumeWriteStream: Boolean</code></h3>
<p>是否等待 <code>writeStream</code> 完全写完才算响应全部接收完毕，默认是 <code>true</code>。
此参数不建议修改默认值，除非我们明确知道它的副作用是可接受的，
否则很可能会导致 <code>writeStream</code> 数据不完整。</p>
<h3 id="method-string"><a class="markdown-anchor" href="#method-string">#</a> <code>method: String</code></h3>
<p>设置请求方法，默认是 <code>GET</code>。
支持 <code>GET、POST、PUT、DELETE、PATCH</code> 等<a href="https://nodejs.org/api/http.html#http_http_methods" target="_blank" rel="noopener">所有 HTTP 方法</a>。</p>
<h3 id="contenttype-string"><a class="markdown-anchor" href="#contenttype-string">#</a> <code>contentType: String</code></h3>
<p>设置请求数据格式，默认是 <code>undefined</code>，HttpClient 会自动根据 <code>data</code> 和 <code>content</code> 参数自动设置。
<code>data</code> 是 object 的时候默认设置的是 <code>form</code>。支持 <code>json</code> 格式。</p>
<p>如需要以 JSON 格式发送 <code>data</code>：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    foo: <span class="string">'bar'</span>,</span><br><span class="line">    now: <span class="built_in">Date</span>.now(),</span><br><span class="line">  &#125;,</span><br><span class="line">  contentType: <span class="string">'json'</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="datatype-string"><a class="markdown-anchor" href="#datatype-string">#</a> <code>dataType: String</code></h3>
<p>设置响应数据格式，默认不对响应数据做任何处理，直接返回原始的 buffer 格式数据。
支持 <code>text</code> 和 <code>json</code> 两种格式。</p>
<p><strong>注意：设置成 <code>json</code> 时，如果响应数据解析失败会抛 <code>JSONResponseFormatError</code> 异常。</strong></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> jsonResult = <span class="keyword">await</span> ctx.curl(url, &#123;</span><br><span class="line">  dataType: <span class="string">'json'</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(jsonResult.data);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> htmlResult = <span class="keyword">await</span> ctx.curl(url, &#123;</span><br><span class="line">  dataType: <span class="string">'text'</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(htmlResult.data);</span><br></pre></td></tr></table></figure>
<h3 id="fixjsonctlchars-boolean"><a class="markdown-anchor" href="#fixjsonctlchars-boolean">#</a> <code>fixJSONCtlChars: Boolean</code></h3>
<p>是否自动过滤响应数据中的特殊控制字符 (U+0000 ~ U+001F)，默认是 <code>false</code>。
通常一些 CGI 系统返回的 JSON 数据会包含这些特殊控制字符，通过此参数可以自动过滤掉它们。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  fixJSONCtlChars: <span class="literal">true</span>,</span><br><span class="line">  dataType: <span class="string">'json'</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="headers-object"><a class="markdown-anchor" href="#headers-object">#</a> <code>headers: Object</code></h3>
<p>自定义请求头。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">'x-foo'</span>: <span class="string">'bar'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="timeout-numberarray"><a class="markdown-anchor" href="#timeout-numberarray">#</a> <code>timeout: Number|Array</code></h3>
<p>请求超时时间，默认是 <code>[ 5000, 5000 ]</code>，即创建连接超时是 5 秒，接收响应超时是 5 秒。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  <span class="comment">// 创建连接超时 3 秒，接收响应超时 3 秒</span></span><br><span class="line">  timeout: <span class="number">3000</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  <span class="comment">// 创建连接超时 1 秒，接收响应超时 30 秒，用于响应比较大的场景</span></span><br><span class="line">  timeout: [ <span class="number">1000</span>, <span class="number">30000</span> ],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="agent-httpagent"><a class="markdown-anchor" href="#agent-httpagent">#</a> <code>agent: HttpAgent</code></h3>
<p>允许通过此参数覆盖默认的 HttpAgent，如果你不想开启 KeepAlive，可以设置此参数为 <code>false</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  agent: <span class="literal">false</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="httpsagent-httpsagent"><a class="markdown-anchor" href="#httpsagent-httpsagent">#</a> <code>httpsAgent: HttpsAgent</code></h3>
<p>允许通过此参数覆盖默认的 HttpsAgent，如果你不想开启 KeepAlive，可以设置此参数为 <code>false</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  httpsAgent: <span class="literal">false</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="auth-string"><a class="markdown-anchor" href="#auth-string">#</a> <code>auth: String</code></h3>
<p>简单登录授权（Basic Authentication）参数，将以明文方式将登录信息以 <code>Authorization</code> 请求头发送出去。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  <span class="comment">// 参数必须按照 `user:password` 格式设置</span></span><br><span class="line">  auth: <span class="string">'foo:bar'</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="digestauth-string"><a class="markdown-anchor" href="#digestauth-string">#</a> <code>digestAuth: String</code></h3>
<p>摘要登录授权（Digest Authentication）参数，设置此参数会自动对 401 响应尝试生成 <code>Authorization</code> 请求头，
尝试以授权方式请求一次。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  <span class="comment">// 参数必须按照 `user:password` 格式设置</span></span><br><span class="line">  digestAuth: <span class="string">'foo:bar'</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="followredirect-boolean"><a class="markdown-anchor" href="#followredirect-boolean">#</a> <code>followRedirect: Boolean</code></h3>
<p>是否自动跟进 3xx 的跳转响应，默认是 <code>false</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  followRedirect: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="maxredirects-number"><a class="markdown-anchor" href="#maxredirects-number">#</a> <code>maxRedirects: Number</code></h3>
<p>设置最大自动跳转次数，避免循环跳转无法终止，默认是 10 次。
此参数不宜设置过大，它只在 <code>followRedirect=true</code> 情况下才会生效。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  followRedirect: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// 最大只允许自动跳转 5 次。</span></span><br><span class="line">  maxRedirects: <span class="number">5</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="formatredirecturl-functionfrom-to"><a class="markdown-anchor" href="#formatredirecturl-functionfrom-to">#</a> <code>formatRedirectUrl: Function(from, to)</code></h3>
<p>允许我们通过 <code>formatRedirectUrl</code> 自定义实现 302、301 等跳转 url 拼接， 默认是 <code>url.resolve(from, to)</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  formatRedirectUrl: <span class="function">(<span class="params"><span class="keyword">from</span>, to</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 例如可在这里修正跳转不正确的 url</span></span><br><span class="line">    <span class="keyword">if</span> (to === <span class="string">'//foo/'</span>) &#123;</span><br><span class="line">      to = <span class="string">'/foo'</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> url.resolve(<span class="keyword">from</span>, to);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="beforerequest-functionoptions"><a class="markdown-anchor" href="#beforerequest-functionoptions">#</a> <code>beforeRequest: Function(options)</code></h3>
<p>HttpClient 在请求正式发送之前，会尝试调用 <code>beforeRequest</code> 钩子，允许我们在这里对请求参数做最后一次修改。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  beforeRequest: <span class="function"><span class="params">options</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 例如我们可以设置全局请求 id，方便日志跟踪</span></span><br><span class="line">    options.headers[<span class="string">'x-request-id'</span>] = uuid.v1();</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="streaming-boolean"><a class="markdown-anchor" href="#streaming-boolean">#</a> <code>streaming: Boolean</code></h3>
<p>是否直接返回响应流，默认为 <code>false</code>。
开启 streaming 之后，HttpClient 会在拿到响应对象 res 之后马上返回，
此时 <code>result.headers</code> 和 <code>result.status</code> 已经可以读取到，只是没有读取 data 数据而已。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(url, &#123;</span><br><span class="line">  streaming: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(result.status, result.data);</span><br><span class="line"><span class="comment">// result.res 是一个 ReadStream 对象</span></span><br><span class="line">ctx.body = result.res;</span><br></pre></td></tr></table></figure>
<p><strong>注意：如果 res 不是直接传递给 body，那么我们必须消费这个 stream，并且要做好 error 事件处理。</strong></p>
<h3 id="gzip-boolean"><a class="markdown-anchor" href="#gzip-boolean">#</a> <code>gzip: Boolean</code></h3>
<p>是否支持 gzip 响应格式，默认为 <code>false</code>。
开启 gzip 之后，HttpClient 将自动设置 <code>Accept-Encoding: gzip</code> 请求头，
并且会自动解压带 <code>Content-Encoding: gzip</code> 响应头的数据。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.curl(url, &#123;</span><br><span class="line">  gzip: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="timing-boolean"><a class="markdown-anchor" href="#timing-boolean">#</a> <code>timing: Boolean</code></h3>
<p>是否开启请求各阶段的时间测量，默认为 <code>false</code>。
开启 timing 之后，可以通过 <code>result.res.timing</code> 拿到这次 HTTP 请求各阶段的时间测量值（单位是毫秒），
通过这些测量值，我们可以非常方便地定位到这次请求最慢的环境发生在那个阶段，效果如同 Chrome network timing 的作用。</p>
<p>timing 各阶段测量值解析：</p>
<ul>
<li>queuing：分配 socket 耗时</li>
<li>dnslookup：DNS 查询耗时</li>
<li>connected：socket 三次握手连接成功耗时</li>
<li>requestSent：请求数据完整发送完毕耗时</li>
<li>waiting：收到第一个字节的响应数据耗时</li>
<li>contentDownload：全部响应数据接收完毕耗时</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> ctx.curl(url, &#123;</span><br><span class="line">  timing: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(result.res.timing);</span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//   "queuing":29,</span></span><br><span class="line"><span class="comment">//   "dnslookup":37,</span></span><br><span class="line"><span class="comment">//   "connected":370,</span></span><br><span class="line"><span class="comment">//   "requestSent":1001,</span></span><br><span class="line"><span class="comment">//   "waiting":1833,</span></span><br><span class="line"><span class="comment">//   "contentDownload":3416</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure>
<h3 id="carejectunauthorizedpfxkeycertpassphrasecipherssecureprotocol"><a class="markdown-anchor" href="#carejectunauthorizedpfxkeycertpassphrasecipherssecureprotocol">#</a> <code>ca，rejectUnauthorized，pfx，key，cert，passphrase，ciphers，secureProtocol</code></h3>
<p>这几个都是透传给 <a href="https://nodejs.org/api/https.html" target="_blank" rel="noopener">HTTPS</a> 模块的参数，具体请查看 <a href="https://nodejs.org/api/https.html#https_https_request_options_callback" target="_blank" rel="noopener"><code>https.request(options, callback)</code></a>。</p>
<h2 id="调试辅助"><a class="markdown-anchor" href="#调试辅助">#</a> 调试辅助</h2>
<p>框架还提供了 <a href="https://github.com/eggjs/egg-development-proxyagent" target="_blank" rel="noopener">egg-development-proxyagent</a> 插件来方便开发者调试。</p>
<p>先安装和开启插件：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i egg-development-proxyagent --save</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/plugin.js</span></span><br><span class="line">exports.proxyagent = &#123;</span><br><span class="line">  enable: <span class="literal">true</span>,</span><br><span class="line">  package: <span class="string">'egg-development-proxyagent'</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>开启抓包工具，可以用 <a href="https://www.charlesproxy.com/" target="_blank" rel="noopener">charles</a> 或 <a href="http://www.telerik.com/fiddler" target="_blank" rel="noopener">fiddler</a>，此处我们用 <a href="https://github.com/alibaba/anyproxy" target="_blank" rel="noopener">anyproxy</a> 来演示下。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm install anyproxy -g</span><br><span class="line">$ anyproxy --port 8888</span><br></pre></td></tr></table></figure>
<p>使用环境变量启动应用：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ http_proxy=http://127.0.0.1:8888 npm run dev</span><br></pre></td></tr></table></figure>
<p>然后就可以正常操作了，所有经过 HttpClient 的请求，都可以在 http://localhost:8002 这个控制台中查看到。</p>
<p><img src="https://cloud.githubusercontent.com/assets/227713/21976937/06a63694-dc0f-11e6-98b5-e9e279c4867c.png" alt="anyproxy"></p>
<p><strong>注意：该插件默认只在 local 环境下启动。</strong></p>
<h2 id="常见错误"><a class="markdown-anchor" href="#常见错误">#</a> 常见错误</h2>
<h3 id="创建连接超时"><a class="markdown-anchor" href="#创建连接超时">#</a> 创建连接超时</h3>
<ul>
<li>异常名称：<code>ConnectionTimeoutError</code></li>
<li>出现场景：通常是 DNS 查询比较慢，或者客户端与服务端之间的网络速度比较慢导致的。</li>
<li>排查建议：请适当增大 <code>timeout</code> 参数。</li>
</ul>
<h3 id="服务响应超时"><a class="markdown-anchor" href="#服务响应超时">#</a> 服务响应超时</h3>
<ul>
<li>异常名称：<code>ResponseTimeoutError</code></li>
<li>出现场景：通常是客户端与服务端之间网络速度比较慢，并且响应数据比较大的情况下会发生。</li>
<li>排查建议：请适当增大 <code>timeout</code> 参数。</li>
</ul>
<h3 id="服务主动断开连接"><a class="markdown-anchor" href="#服务主动断开连接">#</a> 服务主动断开连接</h3>
<ul>
<li>异常名称：<code>ResponseError, code: ECONNRESET</code></li>
<li>出现场景：通常是服务端主动断开 socket 连接，导致 HTTP 请求链路异常。</li>
<li>排查建议：请检查当时服务端是否发生网络异常。</li>
</ul>
<h3 id="服务不可达"><a class="markdown-anchor" href="#服务不可达">#</a> 服务不可达</h3>
<ul>
<li>异常名称：<code>RequestError, code: ECONNREFUSED, status: -1</code></li>
<li>出现场景：通常是因为请求的 url 所属 IP 或者端口无法连接成功。</li>
<li>排查建议：请确保 IP 或者端口设置正确。</li>
</ul>
<h3 id="域名不存在"><a class="markdown-anchor" href="#域名不存在">#</a> 域名不存在</h3>
<ul>
<li>异常名称：<code>RequestError, code: ENOTFOUND, status: -1</code></li>
<li>出现场景：通常是因为请求的 url 所在的域名无法通过 DNS 解析成功。</li>
<li>排查建议：请确保域名存在，也需要排查一下 DNS 服务是否配置正确。</li>
</ul>
<h3 id="json-响应数据格式错误"><a class="markdown-anchor" href="#json-响应数据格式错误">#</a> JSON 响应数据格式错误</h3>
<ul>
<li>异常名称：<code>JSONResponseFormatError</code></li>
<li>出现场景：设置了 <code>dataType=json</code> 并且响应数据不符合 JSON 格式，就会抛出此异常。</li>
<li>排查建议：确保服务端无论在什么情况下都要正确返回 JSON 格式的数据。</li>
</ul>
<h2 id="全局-request-和-response-事件"><a class="markdown-anchor" href="#全局-request-和-response-事件">#</a> 全局 <code>request</code> 和 <code>response</code> 事件</h2>
<p>在企业应用场景，常常会有统一 tracer 日志的需求。
为了方便在 app 层面统一监听 HttpClient 的请求和响应，我们约定了全局 <code>request</code> 和 <code>response</code> 事件来暴露这两个时机。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">init options</span><br><span class="line">    |</span><br><span class="line">    V</span><br><span class="line">emit `request` event</span><br><span class="line">    |</span><br><span class="line">    V</span><br><span class="line">send request and receive response</span><br><span class="line">    |</span><br><span class="line">    V</span><br><span class="line">emit `response` event</span><br><span class="line">    |</span><br><span class="line">    V</span><br><span class="line">   end</span><br></pre></td></tr></table></figure>
<h3 id="request-事件发生在网络操作发生之前"><a class="markdown-anchor" href="#request-事件发生在网络操作发生之前">#</a> <code>request</code> 事件：发生在网络操作发生之前</h3>
<p>请求发送之前，会触发一个 <code>request</code> 事件，允许对请求做拦截。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">app.httpclient.on(<span class="string">'request'</span>, req =&gt; &#123;</span><br><span class="line">  req.url <span class="comment">//请求 url</span></span><br><span class="line">  req.ctx <span class="comment">//是发起这次请求的当前上下文</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 可以在这里设置一些 trace headers，方便全链路跟踪</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="response-事件发生在网络操作结束之后"><a class="markdown-anchor" href="#response-事件发生在网络操作结束之后">#</a> <code>response</code> 事件：发生在网络操作结束之后</h3>
<p>请求结束之后会触发一个 <code>response</code> 事件，这样外部就可以订阅这个事件打印日志。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">app.httpclient.on(<span class="string">'response'</span>, result =&gt; &#123;</span><br><span class="line">  result.res.status</span><br><span class="line">  result.ctx <span class="comment">//是发起这次请求的当前上下文</span></span><br><span class="line">  result.req <span class="comment">//对应的 req 对象，即 request 事件里面那个 req</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h2 id="示例代码"><a class="markdown-anchor" href="#示例代码">#</a> 示例代码</h2>
<p>完整示例代码可以在 <a href="https://github.com/eggjs/examples/blob/master/httpclient" target="_blank" rel="noopener">eggjs/exmaples/httpclient</a> 找到。</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/httpclient.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/httpclient.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
